1
2
3
4
5
6
7 package io.vavr;
8
9 import io.vavr.control.Either;
10 import io.vavr.match.annotation.Patterns;
11 import io.vavr.collection.List;
12 import io.vavr.control.Option;
13 import io.vavr.control.Option.Some;
14 import io.vavr.match.annotation.Unapply;
15 import org.junit.Test;
16
17 import java.math.BigDecimal;
18 import java.time.Year;
19 import java.util.function.Predicate;
20
21 import static io.vavr.API.*;
22 import static io.vavr.Patterns.*;
23 import static io.vavr.MatchTest_DeveloperPatterns.$Developer;
24 import static io.vavr.Predicates.*;
25 import static org.assertj.core.api.Assertions.assertThat;
26
27 public class MatchTest {
28
29
30
31 @Test(expected = MatchError.class)
32 public void shouldThrowIfNotMatching() {
33 Match(new Object()).of(
34 Case($(ignored -> false), o -> null)
35 );
36 }
37
38
39
40 @Test
41 public void shouldMatchNullWithAnyReturningValue() {
42 final Match.Case<Object, Integer> _case = Case($(), 1);
43 final Object obj = null;
44 assertThat(_case.isDefinedAt(obj)).isTrue();
45 assertThat(_case.apply(obj)).isEqualTo(1);
46 }
47
48 @Test
49 public void shouldMatchAnyReturningValue() {
50 final Match.Case<Object, Integer> _case = Case($(), 1);
51 final Object obj = new Object();
52 assertThat(_case.isDefinedAt(obj)).isTrue();
53 assertThat(_case.apply(obj)).isEqualTo(1);
54 }
55
56 @Test
57 public void shouldMatchNullWithAnyReturningAppliedFunction() {
58 final Match.Case<Object, Integer> _case = Case($(), o -> 1);
59 final Object obj = null;
60 assertThat(_case.isDefinedAt(obj)).isTrue();
61 assertThat(_case.apply(obj)).isEqualTo(1);
62 }
63
64 @Test
65 public void shouldMatchAnyReturningAppliedFunction() {
66 final Match.Case<Object, Integer> _case = Case($(), o -> 1);
67 final Object obj = new Object();
68 assertThat(_case.isDefinedAt(obj)).isTrue();
69 assertThat(_case.apply(obj)).isEqualTo(1);
70 }
71
72 @Test
73 public void shouldTakeFirstMatch() {
74 final String actual = Match(new Object()).of(
75 Case($(), "first"),
76 Case($(), "second")
77 );
78 assertThat(actual).isEqualTo("first");
79 }
80
81
82
83 @Test
84 public void shouldMatchValueReturningValue() {
85 final Object obj = new Object();
86 final Match.Case<Object, Integer> _case = Case($(obj), 1);
87 assertThat(_case.isDefinedAt(obj)).isTrue();
88 assertThat(_case.apply(obj)).isEqualTo(1);
89 }
90
91 @Test
92 public void shouldMatchValueReturningValue_NegativeCase() {
93 final Object obj = new Object();
94 final Match.Case<Object, Integer> _case = Case($(obj), 1);
95 assertThat(_case.isDefinedAt(new Object())).isFalse();
96 }
97
98 @Test
99 public void shouldMatchValueReturningAppliedFunction() {
100 final Object obj = new Object();
101 final Match.Case<Object, Integer> _case = Case($(obj), o -> 1);
102 assertThat(_case.isDefinedAt(obj)).isTrue();
103 assertThat(_case.apply(obj)).isEqualTo(1);
104 }
105
106 @Test
107 public void shouldMatchValueReturningAppliedFunction_NegativeCase() {
108 final Object obj = new Object();
109 final Match.Case<Object, Integer> _case = Case($(obj), o -> 1);
110 assertThat(_case.isDefinedAt(new Object())).isFalse();
111 }
112
113
114
115 @Test
116 public void shouldMatchPredicateReturningValue() {
117 final Object obj = new Object();
118 final Match.Case<Object, Integer> _case = Case($(is(obj)), 1);
119 assertThat(_case.isDefinedAt(obj)).isTrue();
120 assertThat(_case.apply(obj)).isEqualTo(1);
121 }
122
123 @Test
124 public void shouldMatchPredicateReturningValue_NegativeCase() {
125 final Object obj = new Object();
126 final Match.Case<Object, Integer> _case = Case($(is(obj)), 1);
127 assertThat(_case.isDefinedAt(new Object())).isFalse();
128 }
129
130 @Test
131 public void shouldMatchPredicateReturningAppliedFunction() {
132 final Object obj = new Object();
133 final Match.Case<Object, Integer> _case = Case($(is(obj)), o -> 1);
134 assertThat(_case.isDefinedAt(obj)).isTrue();
135 assertThat(_case.apply(obj)).isEqualTo(1);
136 }
137
138 @Test
139 public void shouldMatchPredicateReturningAppliedFunction_NegativeCase() {
140 final Object obj = new Object();
141 final Match.Case<Object, Integer> _case = Case($(is(obj)), o -> 1);
142 assertThat(_case.isDefinedAt(new Object())).isFalse();
143 }
144
145
146
147
148
149
150
151
152
153 @Test
154 public void shouldMatchIntUsingPatterns() {
155 final String actual = Match(3).of(
156 Case($(1), "one"),
157 Case($(2), "two"),
158 Case($(), "many")
159 );
160 assertThat(actual).isEqualTo("many");
161 }
162
163 @Test
164 public void shouldMatchIntUsingPredicates() {
165 final String actual = Match(3).of(
166 Case($(is(1)), "one"),
167 Case($(is(2)), "two"),
168 Case($(), "many")
169 );
170 assertThat(actual).isEqualTo("many");
171 }
172
173 @Test
174 public void shouldComputeUpperBoundOfReturnValue() {
175 final Number num = Match(3).of(
176 Case($(is(1)), 1),
177 Case($(is(2)), 2.0),
178 Case($(), i -> new BigDecimal("" + i))
179 );
180 assertThat(num).isEqualTo(new BigDecimal("3"));
181 }
182
183
184
185 @Test
186 public void shouldMatchUsingInstanceOf() {
187 final Object obj = 1;
188 final int actual = Match(obj).of(
189 Case($(instanceOf(Year.class)), y -> 0),
190 Case($(instanceOf(Integer.class)), i -> 1)
191 );
192 assertThat(actual).isEqualTo(1);
193 }
194
195
196
197 @Test
198 public void shouldMatchLeft() {
199 final Either<Integer, String> either = Either.left(1);
200 final String actual = Match(either).of(
201 Case($Left($()), l -> "left: " + l),
202 Case($Right($()), r -> "right: " + r)
203 );
204 assertThat(actual).isEqualTo("left: 1");
205 }
206
207 @Test
208 public void shouldMatchRight() {
209 final Either<Integer, String> either = Either.right("a");
210 final String actual = Match(either).of(
211 Case($Left($()), l -> "left: " + l),
212 Case($Right($()), r -> "right: " + r)
213 );
214 assertThat(actual).isEqualTo("right: a");
215 }
216
217
218
219 @Test
220 public void shouldMatchSome() {
221 final Option<Integer> opt = Option.some(1);
222 final String actual = Match(opt).of(
223 Case($None(), "no value"),
224 Case($Some($()), String::valueOf)
225 );
226 assertThat(actual).isEqualTo("1");
227 }
228
229 @Test
230 public void shouldMatchNone() {
231 final Option<Integer> opt = Option.none();
232 final String actual = Match(opt).of(
233 Case($Some($()), String::valueOf),
234 Case($None(), "no value")
235 );
236 assertThat(actual).isEqualTo("no value");
237 }
238
239 @Test
240 public void shouldDecomposeSomeTuple() {
241 final Option<Tuple2<String, Integer>> tuple2Option = Option.of(Tuple.of("Test", 123));
242 final Tuple2<String, Integer> actual = Match(tuple2Option).of(
243 Case($Some($()), value -> {
244 @SuppressWarnings("UnnecessaryLocalVariable")
245 final Tuple2<String, Integer> tuple2 = value;
246 return tuple2;
247 })
248 );
249 assertThat(actual).isEqualTo(Tuple.of("Test", 123));
250 }
251
252 @Test
253 public void shouldDecomposeSomeSomeTuple() {
254 final Option<Option<Tuple2<String, Integer>>> tuple2OptionOption = Option.of(Option.of(Tuple.of("Test", 123)));
255 final Some<Tuple2<String, Integer>> actual = Match(tuple2OptionOption).of(
256 Case($Some($Some($(Tuple.of("Test", 123)))), value -> {
257 @SuppressWarnings("UnnecessaryLocalVariable")
258 final Some<Tuple2<String, Integer>> some = value;
259 return some;
260 })
261 );
262 assertThat(actual).isEqualTo(Option.of(Tuple.of("Test", 123)));
263 }
264
265
266
267 @Test
268 public void shouldDecomposeEmptyList() {
269 final List<Integer> list = List.empty();
270 final boolean isEmpty = Match(list).of(
271 Case($Cons($(), $()), (x, xs) -> false),
272 Case($Nil(), true)
273 );
274 assertThat(isEmpty).isTrue();
275 }
276
277 @Test
278 public void shouldDecomposeNonEmptyList() {
279 final List<Integer> list = List.of(1);
280 final boolean isNotEmpty = Match(list).of(
281 Case($Nil(), false),
282 Case($Cons($(), $()), (x, xs) -> true)
283 );
284 assertThat(isNotEmpty).isTrue();
285 }
286
287 @SuppressWarnings("UnnecessaryLocalVariable")
288 @Test
289 public void shouldDecomposeListOfTuple3() {
290 final List<Tuple3<String, Integer, Double>> tuple3List = List.of(
291 Tuple.of("begin", 10, 4.5),
292 Tuple.of("middle", 11, 0.0),
293 Tuple.of("end", 12, 1.2));
294 final String actual = Match(tuple3List).of(
295 Case($Cons($(), $()), (x, xs) -> {
296
297 final Tuple3<String, Integer, Double> head = x;
298 final List<Tuple3<String, Integer, Double>> tail = xs;
299 return head + "::" + tail;
300 })
301 );
302 assertThat(actual).isEqualTo("(begin, 10, 4.5)::List((middle, 11, 0.0), (end, 12, 1.2))");
303 }
304
305 @SuppressWarnings("UnnecessaryLocalVariable")
306 @Test
307 public void shouldDecomposeListWithNonEmptyTail() {
308 final List<Option<Number>> intOptionList = List.of(Option.some(1), Option.some(2.0));
309 final String actual = Match(intOptionList).of(
310 Case($Cons($Some($(1)), $Cons($Some($(2.0)), $())), (x, xs) -> {
311
312 final Some<Number> head = x;
313 final List<Option<Number>> tail = xs;
314 return head + "::" + tail;
315 })
316 );
317 assertThat(actual).isEqualTo("Some(1)::List(Some(2.0))");
318 }
319
320
321
322 @Test
323 public void shouldRunUnitOfWork() {
324
325 class OuterWorld {
326
327 String effect = null;
328
329 void displayHelp() {
330 effect = "help";
331 }
332
333 void displayVersion() {
334 effect = "version";
335 }
336 }
337
338 final OuterWorld outerWorld = new OuterWorld();
339
340 Match("-v").of(
341 Case($(isIn("-h", "--help")), o -> run(outerWorld::displayHelp)),
342 Case($(isIn("-v", "--version")), o -> run(outerWorld::displayVersion)),
343 Case($(), o -> { throw new IllegalArgumentException(); })
344 );
345
346 assertThat(outerWorld.effect).isEqualTo("version");
347 }
348
349 @Test
350 public void shouldRunWithInferredArguments() {
351
352 class OuterWorld {
353
354 Number effect = null;
355
356 void writeInt(int i) {
357 effect = i;
358 }
359
360 void writeDouble(double d) {
361 effect = d;
362 }
363 }
364
365 final OuterWorld outerWorld = new OuterWorld();
366 final Object obj = .1d;
367
368 Match(obj).of(
369 Case($(instanceOf(Integer.class)), i -> run(() -> outerWorld.writeInt(i))),
370 Case($(instanceOf(Double.class)), d -> run(() -> outerWorld.writeDouble(d))),
371 Case($(), o -> { throw new NumberFormatException(); })
372 );
373
374 assertThat(outerWorld.effect).isEqualTo(.1d);
375 }
376
377
378
379 @Test
380 public void shouldMatchCustomTypeWithUnapplyMethod() {
381 final Person person = new Developer("Daniel", true, Option.some(13));
382 final String actual = Match(person).of(
383 Case($Developer($("Daniel"), $(true), $()), Person.Util::devInfo),
384 Case($(), p -> "Unknown person: " + p.getName())
385 );
386 assertThat(actual).isEqualTo("Daniel is caffeinated.");
387 }
388
389 interface Person {
390 String getName();
391
392 class Util {
393 static String devInfo(String name, boolean isCaffeinated, Option<Number> number) {
394 return name + " is " + (isCaffeinated ? "" : "not ") + "caffeinated.";
395 }
396 }
397 }
398
399 static final class Developer implements Person {
400 private final String name;
401 private final boolean isCaffeinated;
402 private final Option<Number> number;
403
404 Developer(String name, boolean isCaffeinated, Option<Number> number) {
405 this.name = name;
406 this.isCaffeinated = isCaffeinated;
407 this.number = number;
408 }
409
410 public String getName() { return name; }
411
412 public boolean isCaffeinated() { return isCaffeinated; }
413
414 public Option<Number> number() { return number; }
415
416 @Patterns
417 static class $ {
418 @Unapply
419 static Tuple3<String, Boolean, Option<Number>> Developer(Developer dev) {
420 return Tuple.of(dev.getName(), dev.isCaffeinated(), dev.number());
421 }
422 }
423 }
424
425
426
427 @Test
428 public void shouldNotAmbiguous() {
429
430 {
431
432 assertThat(Case($("1"), () -> "ok").apply("1")).isEqualTo("ok");
433 assertThat(Case($("1"), "ok").apply("1")).isEqualTo("ok");
434 }
435
436 {
437 Predicate<String> p = s -> true;
438 assertThat(Case($(p), o -> "ok").apply("1")).isEqualTo("ok");
439 assertThat(Case($(p), () -> "ok").apply("1")).isEqualTo("ok");
440 assertThat(Case($(p), "ok").apply("1")).isEqualTo("ok");
441 }
442
443 {
444 assertThat(Case($(o -> true), o -> "ok").apply("1")).isEqualTo("ok");
445 assertThat(Case($(o -> true), () -> "ok").apply("1")).isEqualTo("ok");
446 assertThat(Case($(o -> true), "ok").apply("1")).isEqualTo("ok");
447 }
448
449 {
450 assertThat(Case($("1"), o -> "ok").apply("1")).isEqualTo("ok");
451 assertThat(Case($("1"), () -> "ok").apply("1")).isEqualTo("ok");
452 assertThat(Case($("1"), "ok").apply("1")).isEqualTo("ok");
453 }
454 }
455 }